/**
 *
 */
package com.fita.webpay.handler.impl;

import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.utils.JsonUtils;
import com.demo2do.core.wechat.entity.pay.PayTransaction;
import com.fita.webpay.entity.PayChannel;
import com.fita.webpay.entity.PaymentContext;
import com.fita.webpay.entity.callback.CallbackRequest;
import com.fita.webpay.entity.callback.CallbackResponse;
import com.fita.webpay.entity.prepare.PrepareRequest;
import com.fita.webpay.entity.prepare.PrepareResponse;
import com.fita.webpay.handler.PaymentFacade;
import com.fita.webpay.helper.SignatureHelper;
import com.fita.webpay.processor.BusinessProcessor;
import com.fita.webpay.processor.PaymentProcessor;
import com.fita.webpay.service.PaymentService;

/**
 * @author downpour
 *
 */
@Component("paymentFacade")
public class PaymentFacadeImpl implements PaymentFacade {

    private final Logger logger = LoggerFactory.getLogger(PaymentFacadeImpl.class);

    @Autowired
    private Map<String, PaymentProcessor> paymentProcessors;

    @Autowired
    private Map<String, SignatureHelper> signatureHelpers;
    
    @Autowired
    private Map<String, BusinessProcessor> businessProcessors;

    @Autowired
    private PaymentService paymentService;

    /* (non-Javadoc)
     * @see com.fita.webpay.handler.PaymentFacade#prepare(com.fita.webpay.entity.prepare.PrepareRequest)
     */
    public PrepareResponse prepare(PrepareRequest prepareRequest) {
        
        PaymentContext paymentContext = paymentService.prepareContext(prepareRequest);

        // select payment processor
        PaymentProcessor paymentProcessor = selectPaymentProcessor(paymentContext.getPayChannel());

        if(paymentProcessor == null) {
            throw new BusinessException("500", "未能支持的支付渠道类型");
        }
        
        return paymentProcessor.prepare(paymentContext);

    }
    
    /* (non-Javadoc)
     * @see com.fita.webpay.handler.PaymentFacade#validate(com.fita.webpay.entity.PaymentContext, com.fita.webpay.entity.callback.CallbackRequest)
     */
    public void validate(PaymentContext paymentContext, CallbackRequest callbackRequest) {
        
        // 0. get out_trade_no and fee
        PayTransaction payTransaction = callbackRequest.getPayTransaction();
        
        String outTradeNo = payTransaction.getOutTradeNo();
        int fee = payTransaction.getAmount();
        
        logger.trace("#validate with paymentContext[{}], callbackRequest[{}]", JsonUtils.toJson(paymentContext), JsonUtils.toJson(callbackRequest));
        
        logger.trace("#validate - outTradeNo[{}], amount[{}]", outTradeNo, fee);
        
        // 1. validate payment_context to see whether it matches callback_request
        if (paymentContext != null && paymentContext.matches(outTradeNo, fee)) {
            
            // Select concrete signature helper and processor
            PayChannel channel = callbackRequest.getPayChannel();
            
            SignatureHelper signatureHelper = this.selectSignatureHelper(channel);
            PaymentProcessor paymentProcessor = this.selectPaymentProcessor(channel);
            
            logger.trace("signatureHelper [{}]", signatureHelper);
            logger.trace("paymentProcessor [{}]", paymentProcessor);
            
            if (signatureHelper != null && paymentProcessor != null) {

                // 2. get sorted_query_string and check signature
                String sortedQueryString = null;
                if(channel.equals(PayChannel.HANDPAY)) {
                    sortedQueryString = callbackRequest.generateSignString();
                    logger.info("sortedQueryString is [{}]", sortedQueryString);
                } else {
                    sortedQueryString = com.demo2do.core.utils.StringUtils.toSortedQueryString(callbackRequest.getRequestParameters(), "sign", "sign_type");
                }
                
                boolean valid = signatureHelper.validate(sortedQueryString, callbackRequest.getSign());
                
                if (valid) {

                    // 3. check payment config
                    valid = paymentProcessor.validate(callbackRequest.getPayConfig());
                
                    if(!valid) {
                        
                        logger.error("#paid - can not match payment config.");
                        
                        throw BusinessException.newInstance("400", "支付参数无法匹配");
                        
                    }
                    
                } else {
                    
                    logger.error("#paid - signature is not valid.");
                    
                    throw BusinessException.newInstance("400", "参数签名不正确");
                    
                }

            } else {
                
                logger.error("#paid - can not support PayChannel[{}].", channel);
                throw BusinessException.newInstance("400", "无法支持的支付渠道");
            }
            
        } else {
            
            logger.error("#paid - callback request does not match payment context.");
            
            throw BusinessException.newInstance("400", "支付环境不匹配");
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.fita.webpay.handler.PaymentFacade#paid(com.fita.webpay.entity.PaymentContext, com.demo2do.core.wechat.entity.pay.PayTransaction)
     */
	public CallbackResponse paid(PaymentContext paymentContext, PayTransaction payTransaction) {
        
        CallbackResponse response = new CallbackResponse();
        
        // make local database operation and remote business sync into one atom operation
        // if the local operation fails, let's stop remote call

        String transactionCode = payTransaction.getTransactionCode();
        String payTimeString = payTransaction.getPaymentTimeAsString();
        
        boolean paid = paymentService.paid(paymentContext, transactionCode);

        if(paid) {
            
            // get fields from payment context
            String requestNo = paymentContext.getToken();
            String orderCode = paymentContext.getOrderCode();
            int amount = paymentContext.getAmount();
            
            logger.trace("#finish - start notice business with requestNo[{}] orderCode[{}] amount[{}] payTimeString", requestNo, orderCode, transactionCode, amount, payTimeString);
            
            // do remote business sync, this is an async call
            PayChannel channel = PayChannel.codeOf(paymentContext.getChannel());
            logger.trace("channel [{}]", channel);
            BusinessProcessor businessProcessor = this.selectBusinessProcessor(channel);
            logger.trace("businessProcessor [{}]", businessProcessor);
            businessProcessor.notice(paymentContext, payTransaction);
            
        }
        
        return response.success();
        
    }

    /**
     * Select concrete payment processor implementation according to channel
     *
     * @param channel
     * @return
     */
    private PaymentProcessor selectPaymentProcessor(PayChannel channel) {
        String channelCode = channel.getCode();
        return paymentProcessors.get(channelCode + "Processor");
    }

    /**
     * Select concrete signature helper implementation by channel
     *
     * @param channel
     * @return
     */
	private SignatureHelper selectSignatureHelper(PayChannel channel) {
        String channelCode = channel.getCode();
        return signatureHelpers.get(channelCode + "SignatureHelper");
    }
    
    /**
     * 
     * @param channel
     * @return
     */
    private BusinessProcessor selectBusinessProcessor(PayChannel channel) {
        String channelCode = channel.getCode();
        return businessProcessors.get(channelCode + "BusinessProcessor");
    }

}
